package it.unibz.inf.ontop.protege.panels;

/*
 * #%L
 * ontop-protege
 * %%
 * Copyright (C) 2009 - 2013 KRDB Research Centre. Free University of Bozen Bolzano.
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */

import it.unibz.inf.ontop.answering.reformulation.input.SPARQLQueryUtility;
import it.unibz.inf.ontop.owlapi.resultset.BooleanOWLResultSet;
import it.unibz.inf.ontop.owlapi.resultset.TupleOWLResultSet;
import it.unibz.inf.ontop.protege.core.OBDAModel;
import it.unibz.inf.ontop.protege.gui.IconLoader;
import it.unibz.inf.ontop.protege.gui.action.OBDADataQueryAction;
import it.unibz.inf.ontop.protege.utils.DialogUtils;
import it.unibz.inf.ontop.utils.OBDAPreferenceChangeListener;
import it.unibz.inf.ontop.utils.querymanager.QueryController;
import org.semanticweb.owlapi.model.OWLException;

import javax.swing.*;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.StyleContext;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

/**
 * Creates a new panel to execute queries. Remember to execute the
 * setResultsPanel function to indicate where to display the results.
 */
public class QueryInterfacePanel extends JPanel implements SavedQueriesPanelListener,
		TableModelListener, OBDAPreferenceChangeListener {

	/**
	 * Variable currentGroup is the group's id to which belongs the selected
	 * query Variable currentId is the query's id that is selected
	 */
	private static final long serialVersionUID = -5902798157183352944L;

	private DefaultStyledDocument styledDocument;

	private OBDADataQueryAction<TupleOWLResultSet> executeSelectAction;
	private OBDADataQueryAction<BooleanOWLResultSet> executeAskAction;
	private OBDADataQueryAction<?> executeGraphQueryAction;
	private OBDADataQueryAction<String> retrieveUCQExpansionAction;
	private OBDADataQueryAction<String> retrieveUCQUnfoldingAction;

	private OBDAModel apic;

	private QueryController qc;

	private double execTime = 0;
	private int fetchSizeCache = 100;

	private String currentGroup = "";  // default value
	private String currentId = "";  // default value

	/**
	 * Creates new form QueryInterfacePanel
	 */
	public QueryInterfacePanel(OBDAModel apic, QueryController qc) {
		this.qc = qc;
		this.apic = apic;

		initComponents();

		StyleContext style = new StyleContext();
		styledDocument = new DefaultStyledDocument(style);

		queryTextPane.setDocument(styledDocument);
		queryTextPane.setBackground(Color.WHITE);
		queryTextPane.setCaretColor(Color.BLACK);
		queryTextPane.addKeyListener(new CTRLEnterKeyListener());
	}

	private class CTRLEnterKeyListener implements KeyListener {
		@Override
		public void keyTyped(KeyEvent e) {
		}
		@Override
		public void keyPressed(KeyEvent e) {
			if ((e.getModifiers() == KeyEvent.CTRL_MASK && e.getKeyCode() == KeyEvent.VK_ENTER)) {
				cmdExecuteQueryActionPerformed(null);
			}
		}
		@Override
		public void keyReleased(KeyEvent e) {
		}
	}

	public void setOBDAModel(OBDAModel api) {
		this.apic = api;
	}

	/**
	 * This method is called from within the constructor to initialize the form.
	 * WARNING: Do NOT modify this code. The content of this method is always
	 * regenerated by the Form Editor.
	 */
	@SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        sparqlPopupMenu = new javax.swing.JPopupMenu();
        getSPARQLExpansion = new javax.swing.JMenuItem();
        getSPARQLSQLExpansion = new javax.swing.JMenuItem();
        pnlQueryButtons = new javax.swing.JPanel();
        pnlExecutionInfo = new javax.swing.JPanel();
        lblExecutionInfo = new javax.swing.JLabel();
        lblShow = new javax.swing.JLabel();
        txtFetchSize = new javax.swing.JTextField();
        chkShowAll = new javax.swing.JCheckBox();
        chkShowShortURI = new javax.swing.JCheckBox();
        cmdAttachPrefix = new javax.swing.JButton();
        cmdExecuteQuery = new javax.swing.JButton();
        cmdSaveChanges = new javax.swing.JButton();
        pnlQueryEditor = new javax.swing.JPanel();
        jLabelHeader = new javax.swing.JLabel();
        jScrollQueryPane = new javax.swing.JScrollPane();
        queryTextPane = new javax.swing.JTextPane();

        sparqlPopupMenu.setComponentPopupMenu(sparqlPopupMenu);

        getSPARQLExpansion.setText("Get expansion this UCQ...");
        getSPARQLExpansion.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                getSPARQLExpansionActionPerformed(evt);
            }
        });
        sparqlPopupMenu.add(getSPARQLExpansion);

        getSPARQLSQLExpansion.setText("Get SQL translation...");
        getSPARQLSQLExpansion.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                getSPARQLSQLExpansionActionPerformed(evt);
            }
        });
        sparqlPopupMenu.add(getSPARQLSQLExpansion);

        setLayout(new java.awt.GridBagLayout());

        pnlQueryButtons.setPreferredSize(new java.awt.Dimension(445, 30));
        pnlQueryButtons.setLayout(new java.awt.GridBagLayout());

        pnlExecutionInfo.setLayout(new java.awt.GridBagLayout());
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(0, 10, 0, 0);
        pnlExecutionInfo.add(lblExecutionInfo, gridBagConstraints);

        lblShow.setText("Show: ");
        pnlExecutionInfo.add(lblShow, new java.awt.GridBagConstraints());

        txtFetchSize.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
        txtFetchSize.setText("100");
        txtFetchSize.setPreferredSize(new java.awt.Dimension(40, 20));
        pnlExecutionInfo.add(txtFetchSize, new java.awt.GridBagConstraints());

        chkShowAll.setText("All");
        chkShowAll.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        chkShowAll.setPreferredSize(new java.awt.Dimension(55, 23));
        chkShowAll.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                chkShowAllActionPerformed(evt);
            }
        });
        pnlExecutionInfo.add(chkShowAll, new java.awt.GridBagConstraints());

        chkShowShortURI.setText("Short IRI");
        chkShowShortURI.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
        chkShowShortURI.setMaximumSize(new java.awt.Dimension(75, 23));
        chkShowShortURI.setMinimumSize(new java.awt.Dimension(75, 23));
        pnlExecutionInfo.add(chkShowShortURI, new java.awt.GridBagConstraints());

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.weightx = 1.0;
        pnlQueryButtons.add(pnlExecutionInfo, gridBagConstraints);

        cmdAttachPrefix.setIcon(IconLoader.getImageIcon("images/attach.png"));
        cmdAttachPrefix.setText("Attach Prefixes");
        cmdAttachPrefix.setBorder(javax.swing.BorderFactory.createEtchedBorder());
        cmdAttachPrefix.setContentAreaFilled(false);
        cmdAttachPrefix.setIconTextGap(5);
        cmdAttachPrefix.setMaximumSize(new java.awt.Dimension(112, 26));
        cmdAttachPrefix.setMinimumSize(new java.awt.Dimension(112, 26));
        cmdAttachPrefix.setPreferredSize(new java.awt.Dimension(112, 26));
        cmdAttachPrefix.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cmdAttachPrefixActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        pnlQueryButtons.add(cmdAttachPrefix, gridBagConstraints);

        cmdExecuteQuery.setIcon(IconLoader.getImageIcon("images/execute.png"));
        cmdExecuteQuery.setMnemonic('x');
        cmdExecuteQuery.setText("Execute");
        cmdExecuteQuery.setBorder(javax.swing.BorderFactory.createEtchedBorder());
        cmdExecuteQuery.setContentAreaFilled(false);
        cmdExecuteQuery.setIconTextGap(5);
        cmdExecuteQuery.setMaximumSize(new java.awt.Dimension(82, 26));
        cmdExecuteQuery.setMinimumSize(new java.awt.Dimension(82, 26));
        cmdExecuteQuery.setPreferredSize(new java.awt.Dimension(82, 26));
        cmdExecuteQuery.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cmdExecuteQueryActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        pnlQueryButtons.add(cmdExecuteQuery, gridBagConstraints);

        cmdSaveChanges.setIcon(IconLoader.getImageIcon("images/save.png"));
        cmdSaveChanges.setText("Save Changes");
        cmdSaveChanges.setBorder(javax.swing.BorderFactory.createEtchedBorder());
        cmdSaveChanges.setContentAreaFilled(false);
        cmdSaveChanges.setIconTextGap(5);
        cmdSaveChanges.setMaximumSize(new java.awt.Dimension(112, 26));
        cmdSaveChanges.setMinimumSize(new java.awt.Dimension(112, 26));
        cmdSaveChanges.setPreferredSize(new java.awt.Dimension(112, 26));
        cmdSaveChanges.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cmdSaveChangesActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
        pnlQueryButtons.add(cmdSaveChanges, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.gridwidth = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        add(pnlQueryButtons, gridBagConstraints);

        pnlQueryEditor.setLayout(new java.awt.BorderLayout());

        jLabelHeader.setFont(new java.awt.Font("Arial", 1, 11)); // NOI18N
        jLabelHeader.setForeground(new java.awt.Color(153, 153, 153));
        jLabelHeader.setText("  Query Editor");
        jLabelHeader.setMaximumSize(new java.awt.Dimension(68, 18));
        jLabelHeader.setMinimumSize(new java.awt.Dimension(68, 18));
        jLabelHeader.setPreferredSize(new java.awt.Dimension(68, 18));
        pnlQueryEditor.add(jLabelHeader, java.awt.BorderLayout.NORTH);

        queryTextPane.setFont(new java.awt.Font("Lucida Console", 0, 14)); // NOI18N
        queryTextPane.setComponentPopupMenu(sparqlPopupMenu);
        jScrollQueryPane.setViewportView(queryTextPane);

        pnlQueryEditor.add(jScrollQueryPane, java.awt.BorderLayout.CENTER);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridwidth = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        add(pnlQueryEditor, gridBagConstraints);
    }// </editor-fold>//GEN-END:initComponents

    private synchronized void chkShowAllActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkShowAllActionPerformed
    	Runnable runner = new Runnable(){
    		public void run(){
    			if (chkShowAll.isSelected()) {
    				fetchSizeCache = getFetchSize();
    				txtFetchSize.setText(0+"");
    				txtFetchSize.setEditable(false);
    			} else {
    				txtFetchSize.setText(fetchSizeCache+"");
    				txtFetchSize.setEditable(true);
    			}
    		}
    	};
    	SwingUtilities.invokeLater(runner);
    }//GEN-LAST:event_chkShowAllActionPerformed

	private void getSPARQLExpansionActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_getSPARQLExpansionActionPerformed
		Thread queryRunnerThread = new Thread(new Runnable() {
			@Override
			public void run() {
				OBDADataQueryAction<?> action = QueryInterfacePanel.this.getRetrieveUCQExpansionAction();
				action.run(queryTextPane.getText());
			}
		});
		queryRunnerThread.start();
	}// GEN-LAST:event_getSPARQLExpansionActionPerformed

	private void getSPARQLSQLExpansionActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_getSPARQLSQLExpansionActionPerformed
		Thread queryRunnerThread = new Thread(() -> {
			OBDADataQueryAction<?> action = QueryInterfacePanel.this.getRetrieveUCQUnfoldingAction();
			action.run(queryTextPane.getText());
		});
		queryRunnerThread.start();
	}// GEN-LAST:event_getSPARQLSQLExpansionActionPerformed

	private void cmdAttachPrefixActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_buttonAdvancedPropertiesActionPerformed
		SelectPrefixPanel dialog = new SelectPrefixPanel(apic.getMutablePrefixManager(), queryTextPane);
		dialog.show();
	}// GEN-LAST:event_buttonAdvancedPropertiesActionPerformed

	private void cmdExecuteQueryActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_buttonExecuteActionPerformed
		try {
			// TODO Handle this such that there is a listener checking the progress of the execution
			Thread queryRunnerThread = new Thread(() -> {
				String queryString = queryTextPane.getText();
                OBDADataQueryAction<?> action = null;
				if (queryString.isEmpty()){
					JOptionPane.showMessageDialog(QueryInterfacePanel.this, "Query editor cannot be empty.");
				} else if (SPARQLQueryUtility.isSelectQuery(queryString)) {
					action = QueryInterfacePanel.this.getExecuteSelectAction();
				} else if (SPARQLQueryUtility.isAskQuery(queryString)){
					action = QueryInterfacePanel.this.getExecuteAskAction();
                } else if ((SPARQLQueryUtility.isConstructQuery(queryString) || SPARQLQueryUtility.isDescribeQuery(queryString)) ){
                    action = QueryInterfacePanel.this.getExecuteGraphQueryAction();
                } else {
                    JOptionPane.showMessageDialog(QueryInterfacePanel.this, "This type of SPARQL expression is not handled. Please use SELECT, ASK, DESCRIBE, or CONSTRUCT.");
                }
				if(action!=null) {
					action.run(queryString);
					execTime = action.getExecutionTime();
					do {
						int rows = action.getNumberOfRows();
						updateStatus(rows);
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
							break;
						}
					} while (action.isRunning());
					int rows = action.getNumberOfRows();
					updateStatus(rows);
				}
            });
			queryRunnerThread.start();
		} catch (Exception e) {
			DialogUtils.showQuickErrorDialog(QueryInterfacePanel.this, e);
		}
	}// GEN-LAST:event_buttonExecuteActionPerformed

	private synchronized void cmdSaveChangesActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_buttonSaveActionPerformed
		final String query = queryTextPane.getText();
		if (!currentId.isEmpty()) {
			if (!currentGroup.isEmpty()) {
				qc.addQuery(query, currentId, currentGroup);
			} else {
				qc.addQuery(query, currentId);
			}
		} else {
			JOptionPane.showMessageDialog(QueryInterfacePanel.this,
					"Please select first the query node that you would like to update",
					"Warning",
					JOptionPane.WARNING_MESSAGE);
		}
	}// GEN-LAST:event_buttonSaveActionPerformed


	private class QueryChanger implements Runnable {
		String new_query;

		QueryChanger(String new_query){
			this.new_query = new_query;
		}
		public void run(){
			queryTextPane.setText(new_query);
		}
	}

	public void selectedQueryChanged(String new_group, String new_query, String new_id) {
			Runnable runner = new QueryChanger(new_query);
			SwingUtilities.invokeLater(runner);
			currentGroup = new_group;
			currentId = new_id;
		}

	public void setExecuteSelectAction(OBDADataQueryAction<TupleOWLResultSet> executeUCQAction) {
		this.executeSelectAction = executeUCQAction;
	}

	public OBDADataQueryAction<?> getExecuteSelectAction() {
		return executeSelectAction;
	}

	public void setExecuteAskAction(OBDADataQueryAction<BooleanOWLResultSet> executeUCQAction) {
		this.executeAskAction = executeUCQAction;
	}

	public OBDADataQueryAction<?> getExecuteAskAction() {
		return executeAskAction;
	}

	public void setExecuteGraphQueryAction(OBDADataQueryAction<?> action) {
		this.executeGraphQueryAction = action;
	}

	public OBDADataQueryAction<?> getExecuteGraphQueryAction() {
		return this.executeGraphQueryAction;
	}


	public void setRetrieveUCQExpansionAction(OBDADataQueryAction<String> retrieveUCQExpansionAction) {
		this.retrieveUCQExpansionAction = retrieveUCQExpansionAction;
	}

	public OBDADataQueryAction<?> getRetrieveUCQExpansionAction() {
		return retrieveUCQExpansionAction;
	}

	public void setRetrieveUCQUnfoldingAction(OBDADataQueryAction<String> retrieveUCQUnfoldingAction) {
		this.retrieveUCQUnfoldingAction = retrieveUCQUnfoldingAction;
	}

	public OBDADataQueryAction<?> getRetrieveUCQUnfoldingAction() {
		return retrieveUCQUnfoldingAction;
	}



	//set the information about the time and the number of results

	private class ExecTimeSetter implements Runnable {
		String s;
		ExecTimeSetter(String s){
			super();
			this.s = s;
		}
		public void run(){
			lblExecutionInfo.setText(s);
			lblExecutionInfo.setOpaque(false);
		}
	}

	//get and update the info box with  the actual time in seconds of the execution of the query
	public void updateStatus(long result) {
		if(result != - 1) {
			Double time = execTime / 1000;
			String s = String.format("Execution time: %s sec - Number of rows retrieved: %,d ", time, result);
			Runnable time_setter = new ExecTimeSetter(s);
			SwingUtilities.invokeLater(time_setter);
		}
	}

	//set the boolean result in the info box of the ask query
	private class AskQueryInfoSetter implements Runnable{
		String title;
		BooleanOWLResultSet result;



		AskQueryInfoSetter(String title, BooleanOWLResultSet result){
			super();
			this.title = title;
			this.result = result;
		}
		@Override
		public void run()
		{
			Double time = execTime / 1000;
			String s = String.format("Execution time: %s sec - ", time);
			String answer;
			try {
				if(result.getValue()){
                    answer = "true";
                    lblExecutionInfo.setBackground(Color.GREEN);
					lblExecutionInfo.setOpaque(true);
                }
                else{
                    answer = "false";
                    lblExecutionInfo.setBackground(Color.RED);
					lblExecutionInfo.setOpaque(true);
                }

				lblExecutionInfo.setText(s+ title+ " "+ answer);

			} catch (OWLException e) {
				JOptionPane.showMessageDialog(
						QueryInterfacePanel.this,
						e);
			}

		}

	}

	//show the result for ask query

	public  void showBooleanActionResultInTextInfo(String title, BooleanOWLResultSet result) throws OWLException {

		AskQueryInfoSetter alter_result_panel = new AskQueryInfoSetter(title, result);
		SwingUtilities.invokeLater(alter_result_panel);
	}

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JCheckBox chkShowAll;
    private javax.swing.JCheckBox chkShowShortURI;
    private javax.swing.JButton cmdAttachPrefix;
    private javax.swing.JButton cmdExecuteQuery;
    private javax.swing.JButton cmdSaveChanges;
    private javax.swing.JMenuItem getSPARQLExpansion;
    private javax.swing.JMenuItem getSPARQLSQLExpansion;
    private javax.swing.JLabel jLabelHeader;
    private javax.swing.JScrollPane jScrollQueryPane;
    private javax.swing.JLabel lblExecutionInfo;
    private javax.swing.JLabel lblShow;
    private javax.swing.JPanel pnlExecutionInfo;
    private javax.swing.JPanel pnlQueryButtons;
    private javax.swing.JPanel pnlQueryEditor;
    private javax.swing.JTextPane queryTextPane;
    private javax.swing.JPopupMenu sparqlPopupMenu;
    private javax.swing.JTextField txtFetchSize;
    // End of variables declaration//GEN-END:variables

	//update the number of rows when the table change
	@Override
	public void tableChanged(TableModelEvent e) {
		int rows = ((TableModel) e.getSource()).getRowCount();
		updateStatus(rows);
	}

	public boolean isShortURISelect() {
		return chkShowShortURI.isSelected();
	}

	public boolean isFetchAllSelect() {
		return chkShowAll.isSelected();
	}

	// TODO Remove this method after moving the GUI package to protege41 module.
	// The constant 100 is the same as the NEXT_FETCH_SIZE in OWLResultSetTableModel
	public boolean canGetMoreTuples() {
		return getFetchSize() > 100;
	}

	public String getQuery() {
		return queryTextPane.getText();
	}

	public int getFetchSize() {
		int fetchSize = 0;
		try {
			fetchSize = Integer.parseInt(txtFetchSize.getText());
		} catch (NumberFormatException e) {
			DialogUtils.showQuickErrorDialog(this,
					new Exception("Invalid input: " + txtFetchSize.getText()), e.toString());
		}
		return fetchSize;
	}

	private class SetQueryTextPane implements Runnable{
		String query;
		SetQueryTextPane(String query){
			super();
			this.query = query;
		}
		public void run(){
			queryTextPane.setText(query);
		}
	}

	@Override
	public synchronized void preferenceChanged() {
		String query = queryTextPane.getText();
		SetQueryTextPane setter = new SetQueryTextPane(query);
		SwingUtilities.invokeLater(setter);
	}
}
